package net.aiotos.common.ice;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.lang.Exception;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import Glacier2.*;
import Glacier2.RouterPrx;
import Ice.*;
import com.alibaba.fastjson.JSONException;
import net.aiotos.common.dto.*;
import net.aiotos.common.exception.AccountNotRegisterError;
import net.aiotos.common.exception.IONodeNotExistError;
import net.aiotos.common.ice.routellib.CallbackPrx;
import net.aiotos.common.ice.routellib.CallbackPrxHelper;
import net.aiotos.common.ice.routellib.CallbackReceiverDisp;
import net.aiotos.common.util.PostUrl;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import com.alibaba.fastjson.JSON;

/**
 * ice服务类
 * 与平台通信核心类，与平台业务逻辑无关。
 * 封装好返回结构，下游无需再处理。
 *
 * @author zws
 */
@NoArgsConstructor
@Slf4j
public class IceService {
    private CallbackPrx callbackPrx = null;
    @Getter
    private Glacier2.RouterPrx routerPrx = null;
    private Ice.ObjectPrx remoteAdapter = null;
    private Ice.Communicator communicator = null;
    private ObjectAdapter localAdapter = null;
    @Getter
    private Ice.Identity identity = null;
    @Getter
    private String uuidSession = null;
    private String uuid = null;
    private LoginResp.RouterConfigDTO routerConfig = null;
    @Getter
    private int heartbeat = 60;
    @Getter
    private LoginDTO loginDTO;

    private RouterPrx newRouterPrx() throws PermissionDeniedException, CannotCreateSessionException, SessionNotExistException {
        communicator.destroy();
        return newRouterPrx(routerConfig.getIotrouterIP(), routerConfig.getRouterPrxPort());
    }

    private RouterPrx newRouterPrx(String routerIP, int routerPrxPort) throws PermissionDeniedException, CannotCreateSessionException, SessionNotExistException {
        routerPrx = getRouterPrx(communicator, routerIP, String.valueOf(routerPrxPort));
        routerPrx.refreshSession();
        return routerPrx;
    }

    private CallbackPrx newCallbackPrx(String routerIP, int port, final CallbackReceiverDisp callBackReceiver) {
        identity = getNewIdentity();
        callbackPrx = getCallbackPrx(communicator, routerIP, String.valueOf(port), routerPrx, callBackReceiver);
        return callbackPrx;
    }

    private ResultDTO addClient() throws SessionNotExistException, PermissionDeniedException, CannotCreateSessionException {
        return addClient(uuidSession, identity);
    }

    private ResultDTO addClient(String uuidSession) {
        return addClient(uuidSession, identity);
    }

    private ResultDTO addClient(String uuidSession, Ice.Identity identity) {
        String ret = callbackPrx.addClient(uuidSession, identity);// 添加客户
        ResultDTO resultDTO = ResultDTO.toObject(ret);
        return resultDTO;
    }

    private Ice.Communicator getCommunicator(String certDir, String serverIp) throws FileNotFoundException {
        // 设置ICE属性
        InitializationData initData = new InitializationData();
        initData.properties = Ice.Util.createProperties();

        initData.properties.setProperty("IceSSL.Password", "password");
        initData.properties.setProperty("IceSSL.KeystoreType", "JKS");
        initData.properties.setProperty("IceSSL.TruststoreType", "JKS");
        initData.properties.setProperty("Ice.ACM.Server", "0"); // 客户端与服务端保持连接时间（0禁止）
        initData.properties.setProperty("Ice.ACM.Client", "0"); // 服务端与客户端保持连接时间（0禁止）
        initData.properties.setProperty("Ice.Override.ConnectTimeout", "10000");
        initData.properties.setProperty("Ice.Override.Timeout", "30000");
        initData.properties.setProperty("Ice.ThreadPool.Client.Size", "2");
        initData.properties.setProperty("Ice.ThreadPool.Client.SizeMax", "10");
        initData.properties.setProperty("Ice.MessageSizeMax", "10240");
        initData.properties.setProperty("Ice.InitPlugins", "0");
        initData.properties.setProperty("Ice.Plugin.IceSSL", "IceSSL.PluginFactory");
        Ice.Communicator communicator = Ice.Util.initialize(initData);
        IceSSL.Plugin plugin = (IceSSL.Plugin) communicator.getPluginManager().getPlugin("IceSSL");
        String certJksPath = String.format("%s/%s-%s", certDir, serverIp, "client.jks");
        File file = new File(certJksPath);
        if(!(file.exists() && file.isFile())) {
            certJksPath = String.format("%s/%s", certDir, "client.jks");
            file = new File(certJksPath);
            if(!(file.exists() && file.isFile())) {
                throw new FileNotFoundException("客户端证书不存在:" + certJksPath);
            }

        }
        java.io.InputStream certs = new FileInputStream(file);
        plugin.setKeystoreStream(certs);
        plugin.setTruststoreStream(certs);
        communicator.getPluginManager().initializePlugins();

        return communicator;
    }

    private CallbackPrx getCallbackPrx(Ice.Communicator communicator, String iotrouterIP,
                                       String remoteAdapterPort, Glacier2.RouterPrx routerPrx, CallbackReceiverDisp callBackReceiver) {
        String routeArg = "iotroute:tcp -p " + remoteAdapterPort + "  -h " + iotrouterIP;
        remoteAdapter = communicator.stringToProxy(routeArg);
        callbackPrx = CallbackPrxHelper.uncheckedCast(remoteAdapter);
        communicator.setDefaultRouter(routerPrx);
        localAdapter = communicator.createObjectAdapter("");
        localAdapter.add(callBackReceiver, identity);
        localAdapter.activate();
        callbackPrx.ice_getConnection().setAdapter(localAdapter);
        return callbackPrx;
    }

    private Glacier2.RouterPrx getRouterPrx(Ice.Communicator communicator, String iotrouterIP, String routerPrxPort) throws PermissionDeniedException, CannotCreateSessionException {
        String prxArg = "DemoGlacier2/router:ssl -p " + routerPrxPort + " -h " + iotrouterIP;
        Ice.ObjectPrx defaultRouter = communicator.stringToProxy(prxArg);
        routerPrx = Glacier2.RouterPrxHelper.uncheckedCast(defaultRouter.ice_secure(true));
        routerPrx.begin_destroySession(new Callback() {
            @Override
            public void completed(AsyncResult r) {
                log.info("begin_destroySession.completed:{}", r.getOperation());
            }
        });
        routerPrx.createSession("IOTCC", "iotccpasword");// 打洞服务验证用户名密码写死 by 王刚
        final int acmTimeout = routerPrx.getACMTimeout();
        if (acmTimeout > 0) {
            Ice.Connection conn = routerPrx.ice_getCachedConnection();
            conn.setCallback(new ConnectionCallback() {
                @Override
                public void heartbeat(Connection con) {
                    log.info("ConnectionCallback.heartbeat:{}", con.toString());
                }

                @Override
                public void closed(Connection con) {
                    log.info("ConnectionCallback.closed:{}", con.toString());
                }
            });
            conn.setACM(new Ice.IntOptional(acmTimeout),
                    new Ice.Optional<>(Ice.ACMClose.CloseOff),
                    new Ice.Optional<>(Ice.ACMHeartbeat.HeartbeatAlways));
        }
        return routerPrx;
    }

    private Ice.Identity getNewIdentity() {
        identity = new Ice.Identity();
        identity.name = UUID.randomUUID().toString();
        identity.category = "";
        return identity;
    }

    //登录
    public LoginResp login(final LoginDTO loginDTO, final CallbackReceiverDisp callBackReceiver)
            throws Exception {
        LoginResp loginResp = loginWeb(loginDTO); // 登录返回结果
        uuidSession = loginResp.getData().get(0).getTableId();
        Integer code = loginResp.getCode();
        if (code != null && code == 0) {
            //http登录成功
            if (null != communicator) {
                try {
                    communicator.destroy();
                } catch (Exception ex) {
                    log.error("销毁前对象发错错误", ex);
                    throw ex;
                }
                communicator = null;
            }

            routerConfig = loginResp.getRouterConfig();
            heartbeat = loginResp.getHeartbeat();

            communicator = getCommunicator(loginDTO.certDir, routerConfig.getIotrouterIP());

            newRouterPrx(routerConfig.getIotrouterIP(), routerConfig.getRouterPrxPort());
            newCallbackPrx(routerConfig.getIotrouterIP(), routerConfig.getRemoteAdapterPort(), callBackReceiver);

            // 添加客户
            ResultDTO info = addClient(uuidSession, identity);
        }
        return loginResp;
    }

    /**
     * 登录物联中台WEB，获取授权SESSION
     * @return
     * @throws Exception
     */
    public LoginResp loginWeb() throws Exception {
        return loginWeb(loginDTO);
    }

    public LoginResp loginWeb(@NonNull final LoginDTO loginDTO) throws Exception {
        LoginResp loginInfo = null;
        String loginUrl = loginDTO.httpHost + "/api/login"; // 登录
        log.info("===========================loginUrl:" + loginUrl);
        Map<String, String> param = new HashMap<String, String>();
        param.put("username", loginDTO.username);
        param.put("psw", loginDTO.password);
        param.put("uuid", loginDTO.uuid);

        String json = JSON.toJSONString(param);
        String resultData = PostUrl.postMethod(loginUrl, json);

        String uuidSession = null;
        try {
            loginInfo = JSON.parseObject(resultData, LoginResp.class);
        } catch (JSONException ex) {
            log.error("数据结构异常:{}", resultData);
            throw ex;
        }


        if (loginInfo == null) {
            throw new Exception("接口返回结构异常：" + resultData);
        }

        if (loginInfo.getCode() != null && loginInfo.getCode() != 0) {
            if(loginInfo.getCode() == ErrorEnum.AccountNotRegister.code) {
                throw new AccountNotRegisterError();
            }
            if(loginInfo.getCode() == ErrorEnum.IONodeNotExist.code) {
                throw new IONodeNotExistError();
            }
            throw new Exception("登录失败：" + resultData);
        }

        LoginResp.RouterConfigDTO routerConfig = loginInfo.getRouterConfig();

        if (null != loginInfo.getData()) {
            List<LoginResp.DataDTO> dataDTOList = loginInfo.getData();
            if (null != dataDTOList && null != dataDTOList.get(0)) {
                uuidSession = dataDTOList.get(0).getTableId();
            }
        }
        log.info("===========================heartBeat:" + loginInfo.getHeartbeat());
        log.info("===========================iotrouterIP:" + routerConfig.getIotrouterIP());
        log.info("===========================routerPrxPort:" + routerConfig.getRouterPrxPort());
        log.info("===========================remoteAdapterPort:" + routerConfig.getRemoteAdapterPort());
        log.info("===========================uuidSession:" + uuidSession);
        this.uuidSession = uuidSession;
        this.uuid = loginDTO.uuid;
        this.routerConfig = routerConfig;
        this.loginDTO = loginDTO;
        return loginInfo;
    }


    public String sendMsg(String toUuidSession, String data) {
        return callbackPrx.sendMsg(uuid, toUuidSession, data);
    }

    public ResultDTO subMsg(String points) {
        ResultDTO result = null;
        try {
            String retJson = callbackPrx.subMsg(uuidSession, points);
            result = ResultDTO.toObject(retJson);
        } catch (Exception e) {
            result = ResultDTO.code(514).msg("Argument error");
        }
        return result;
    }

    public ResultDTO syncPubMsg(String points) throws Exception {
        String retJson = null;
        try {
            retJson = callbackPrx.syncPubMsg(uuidSession, points);
            List<ResultDTO> resultDTOList = ResultDTO.toArray(retJson);

            if(resultDTOList.size() != 1) {
                log.error("返回数据结构异常:{}", retJson);
                throw new Exception();
            }
            ResultDTO ret = resultDTOList.get(0);
            return ret;
        } catch (JSONException ex) {
            // 返回结构不固定，如是Object,非array
            ResultDTO resultDTO = ResultDTO.toObject(retJson);
            return resultDTO;
        } catch (Exception ex) {
            throw ex;
        }
    }

    public IonodeDTO getPlatformData(String points) {
        String retJson = callbackPrx.getPlatformData(uuidSession, points);
        IonodeDTO ionodeDTO = ResultDTO.toJavaObject(retJson, IonodeDTO.class);
        return ionodeDTO;
    }

    public ResultDTO getDeviceData(String points) {
        String retJson = callbackPrx.getDeviceData(uuidSession, points);
        ResultDTO resultDTO = ResultDTO.toObject(retJson);
        return resultDTO;
    }

    public ResultDTO devOnline(String devId) {
        String retJson = callbackPrx.devOnline(uuidSession, devId);
        ResultDTO resultDTO = ResultDTO.toObject(retJson);
        return resultDTO;
    }

    public ResultDTO devOffline(String devId) {
        String retJson = callbackPrx.devOffline(uuidSession, devId);
        ResultDTO resultDTO = ResultDTO.toObject(retJson);
        return resultDTO;
    }

    public ResultDTO getTableDetail(String uuidSession) {
        String retJson = callbackPrx.getTableDetail(uuidSession);
        ResultDTO resultDTO = ResultDTO.toObject(retJson);
        return resultDTO;
    }

    public ResultDTO getTableDetail() {
        String retJson = callbackPrx.getTableDetail(uuidSession);
        ResultDTO resultDTO = ResultDTO.toObject(retJson);
        return resultDTO;
    }

    public ResultDTO updateTable(IonodeDTO ionodeDTO) {
        return updateTable(ionodeDTO.toString());
    }

    public ResultDTO updateTable(String tableJsonData) {
        String retJson = callbackPrx.updateTable(uuidSession, tableJsonData);
        ResultDTO resultDTO = ResultDTO.toObject(retJson);
        return resultDTO;
    }

    public HeartbeatDTO heartbeat() {
        String retJson = callbackPrx.heartbeat();
        HeartbeatDTO heartbeatDTO = JSON.parseObject(retJson, HeartbeatDTO.class);
        return heartbeatDTO;
    }

    public ResultDTO logout() {
        String retJson = callbackPrx.logout(uuidSession);
        ResultDTO resultDTO = ResultDTO.toObject(retJson);
        return resultDTO;
    }


    public void destroy() throws SessionNotExistException {
        routerPrx.destroySession();
    }

}

